/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string>

#include "yaffut.h"
#include "myassert.h"
#include "TestConnection.h"
#include "TestDatabase.h"

#define private public
#include "PlayerSession.h"
#undef private

#include "Command.h"
#include "Parser.h"
#include "config.h"

using namespace std;
using namespace Chronos;


class TestParser : public Parser {
public:
    TestParser() : written("") {}
    virtual void parse(Session&, const char* text) { written += text;}
    virtual void showCommands(Session&) const {}
    virtual void printStatus(StringBuilder&) const {}

    void clear() { written = ""; }
    void checkWritten(const char* expected) {
	    ASSERT_STR_EQUAL(expected, written);
        clear();
    }
    void checkWritten(const std::string& expected) { checkWritten(expected.c_str()); }
    std::string written;
};


struct PlayerSessionTest {
	PlayerSessionTest()
		: connectionClosed(false)
		, connectionClosed2(false)
		, connection(new TestConnection(&connectionClosed))
		, session("player", connection, parser, database)
        , pulse(0)
	{
        session.autoSaveInterval = 10000000;
        session.setPrompt("mud> ");
		ASSERT_FALSE(connectionClosed);
        session.flush();
        connection->CONNECTION_CHECKWRITTEN("Welcome player\nType 'help' to see a list of commands.\nmud> ");
	}

	void testInputResult(unsigned int pulse_, const string& input, const string& conOutput, const string& parserOutput, int result) {
        while (pulse_ != 0 && pulse < pulse_ -1) {
            connection->setOutput("");
            session.handlePulse();
            ++pulse;
        }
		connection->setOutput(input);
        ASSERT_EQUAL(result, session.handlePulse());
        ++pulse;
        session.flush();
        connection->CONNECTION_CHECKWRITTEN(conOutput);
        parser.checkWritten(parserOutput);
	}

	bool connectionClosed;
	bool connectionClosed2;
	TestConnection* connection;
	TestParser parser;
    TestDatabase database;
	PlayerSession session;
    static const unsigned int ACTIVE_TIMEOUT = minutes(5);
    static const unsigned int IDLE_TIMEOUT = minutes(10);
    unsigned int pulse;
};


TEST(PlayerSessionTest, testWriteWhenConnected) {
    session.write() << "test1 " << 10 << ' ' << -10 << "\n";
    session.flush();
    connection->CONNECTION_CHECKWRITTEN("test1 10 -10\n");
    session.write() << "test2\n"; 
    session.write() << "test3\n"; 
    connection->CONNECTION_CHECKWRITTEN("");
    session.flush();
    connection->CONNECTION_CHECKWRITTEN("test2\ntest3\n");
}


TEST(PlayerSessionTest, testDisconnectAndReconnect) {
    testInputResult(1, "ERROR", "", "", PlayerSession::OK);
    ASSERT_TRUE(connectionClosed);
    session.write() << "test test test\n";
    connection = new TestConnection(&connectionClosed);
    testInputResult(101, "", "", "", PlayerSession::OK);
    session.changeConnection(connection);
    session.flush();
    connection->CONNECTION_CHECKWRITTEN("<reconnected....welcome back>\nmud> ");

    testInputResult(101+ACTIVE_TIMEOUT, "", "", "", PlayerSession::OK);
    testInputResult(102+ACTIVE_TIMEOUT, "", "<You are now marked idle>\n", "", PlayerSession::OK);
}


TEST(PlayerSessionTest, testHandleTimeouts) {
    testInputResult(ACTIVE_TIMEOUT-1, "", "", "", PlayerSession::OK);
    testInputResult(ACTIVE_TIMEOUT+1, "", "<You are now marked idle>\n", "", PlayerSession::OK);
    testInputResult(ACTIVE_TIMEOUT+IDLE_TIMEOUT+1, "", "", "", PlayerSession::OK);
    testInputResult(ACTIVE_TIMEOUT+IDLE_TIMEOUT+2, "", "Connection inactive... closing player session\n", "", PlayerSession::FINISHED);
}


TEST(PlayerSessionTest, testWritingPrompt) {
    testInputResult(1, "", "", "", PlayerSession::OK);
    testInputResult(2, "ENTER", "mud> ", "", PlayerSession::OK);
    testInputResult(3, "look", "mud> ", "look", PlayerSession::OK);
	
    connection->setOutput("ERROR");
    ASSERT_EQUAL(PlayerSession::OK, session.handlePulse());
    session.flush();
    parser.checkWritten("");
    ASSERT_TRUE(connectionClosed);
}


TEST(PlayerSessionTest, testIdleActive) {
    testInputResult(ACTIVE_TIMEOUT+1, "", "<You are now marked idle>\n", "", PlayerSession::OK);
    testInputResult(ACTIVE_TIMEOUT+101, "look", "<Removed idle status>\nmud> ", "look", PlayerSession::OK);

    testInputResult(2*ACTIVE_TIMEOUT+101, "", "<You are now marked idle>\n", "", PlayerSession::OK);
    testInputResult(2*ACTIVE_TIMEOUT+201, "ENTER", "<Removed idle status>\nmud> ", "", PlayerSession::OK);
}


TEST(PlayerSessionTest, testReplacingAlias) {
	AliasList& aliases = session.getAliases();

    testInputResult(1, "dw", "mud> ", "dw", PlayerSession::OK);

    aliases.add("dw", "drink wine");
    testInputResult(2, "dw", "mud> ", "drink wine", PlayerSession::OK);
    testInputResult(3, "dw and beer", "mud> ", "drink wine and beer", PlayerSession::OK);

    aliases.add("db", "drink beer");
    testInputResult(4, "db and juice", "mud> ", "drink beer and juice", PlayerSession::OK);
    
    aliases.add("dw", "drink water");
    testInputResult(5, "dw and beer", "mud> ", "drink water and beer", PlayerSession::OK);

    aliases.remove("dw");
    testInputResult(6, "dw", "mud> ", "dw", PlayerSession::OK);

    aliases.add("pure", "fat");
    aliases.add("water", "hamburger");
    testInputResult(7, "pure water", "mud> ", "fat hamburger", PlayerSession::OK);
}


TEST(PlayerSessionTest, testRepeatLastCommand) {
    testInputResult(1, "look", "mud> ", "look", PlayerSession::OK);
    testInputResult(2, "!", "mud> ", "look", PlayerSession::OK);
    testInputResult(3, "!", "mud> ", "look", PlayerSession::OK);

    testInputResult(4, "north", "mud> ", "north", PlayerSession::OK);
    testInputResult(5, "!", "mud> ", "north", PlayerSession::OK);
}

